/* tcc extension for PHP (c) 2022 chenfa */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "ext/standard/info.h"
#include "php_tcc.h"

#ifdef ZTS
#include "TSRM.h"
#endif

/* For compatibility with older PHP versions */
#ifndef ZEND_PARSE_PARAMETERS_NONE
#define ZEND_PARSE_PARAMETERS_NONE() \
	ZEND_PARSE_PARAMETERS_START(0, 0) \
	ZEND_PARSE_PARAMETERS_END()
#endif

static int le_tcc = FAILURE;

static void php_tcc_dtor(zend_resource *rsrc TSRMLS_DC)
{
	php_tcc *tcc = (php_tcc*)rsrc->ptr;
	if(tcc)
    {
		if(tcc->state)
        {
			tcc_delete(tcc->state);
			tcc->state = NULL;
		}
		if(tcc->argv)
        {
			efree(tcc->argv);
			tcc->argv=NULL;
		}
		efree(tcc);
        rsrc->ptr = NULL;
	}
}

void php_tcc_error(void *opaque, const char *msg)
{
	php_printf(msg);
	php_printf("\n");
}
PHP_FUNCTION(tcc_new)
{
    ZEND_PARSE_PARAMETERS_NONE();
    php_tcc *tcc;
    tcc = emalloc(sizeof(php_tcc));
    tcc->argc = 0;
    tcc->argv = NULL;
    tcc->state = tcc_new();
    if(!tcc->state)
    {
        efree(tcc);
        RETURN_FALSE;
    }
    tcc_set_error_func(tcc->state, NULL, php_tcc_error);
    zend_resource *tccResource =  zend_register_resource(tcc, le_tcc);
    RETURN_RES(tccResource);
}

PHP_FUNCTION(tcc_delete)
{
	zval *ztcc;
	php_tcc *tcc=NULL;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_RESOURCE(ztcc);
    ZEND_PARSE_PARAMETERS_END();
	
    
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }
	tcc_delete(tcc->state);
	tcc->state = NULL;
    zend_list_close(Z_RES_P(ztcc));
    RETURN_TRUE;
}
PHP_FUNCTION(tcc_add_sysinclude_path)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *pathname;
	size_t pathname_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(pathname, pathname_len);
    ZEND_PARSE_PARAMETERS_END();
    

    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }

	ret = tcc_add_sysinclude_path(tcc->state, pathname);
	RETURN_LONG(ret);
}
PHP_FUNCTION(tcc_add_include_path)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *pathname;
	size_t pathname_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(pathname, pathname_len);
    ZEND_PARSE_PARAMETERS_END();
    

    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }

	ret = tcc_add_include_path(tcc->state, pathname);
	RETURN_LONG(ret);
}

PHP_FUNCTION(tcc_set_lib_path)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *pathname;
	size_t pathname_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(pathname, pathname_len);
    ZEND_PARSE_PARAMETERS_END();
    

    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }

	tcc_set_lib_path(tcc->state, pathname);
	RETURN_TRUE;
}

PHP_FUNCTION(tcc_add_library_path)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *pathname;
	size_t pathname_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(pathname, pathname_len);
    ZEND_PARSE_PARAMETERS_END();
    

    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }

	ret = tcc_add_library_path(tcc->state, pathname);
	RETURN_LONG(ret);
}

PHP_FUNCTION(tcc_add_library)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *pathname;
	size_t pathname_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(pathname, pathname_len);
    ZEND_PARSE_PARAMETERS_END();
    

    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }

	ret = tcc_add_library(tcc->state, pathname);
	RETURN_LONG(ret);
}

PHP_FUNCTION(tcc_define_symbol)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *sym;
	size_t sym_len;
	char *value;
	size_t value_len;

    ZEND_PARSE_PARAMETERS_START(3, 3)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(sym, sym_len);
        Z_PARAM_STRING(value, value_len);
    ZEND_PARSE_PARAMETERS_END();
	
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }
	tcc_define_symbol(tcc->state, sym, value);
    RETURN_TRUE;
}
PHP_FUNCTION(tcc_undefine_symbol)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *sym;
	size_t sym_len;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(sym, sym_len);
    ZEND_PARSE_PARAMETERS_END();
	
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }
	tcc_undefine_symbol(tcc->state, sym);
    RETURN_TRUE;
}

PHP_FUNCTION(tcc_add_file)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *filename;
	size_t filename_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(filename, filename_len);
    ZEND_PARSE_PARAMETERS_END();
	
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }

	ret = tcc_add_file(tcc->state, filename);
    if(ret==-1)
    {
        php_tcc_dtor(Z_RES_P(ztcc));
    }
	RETURN_LONG(ret);
}


PHP_FUNCTION(tcc_compile_string)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *buf;
	size_t buf_len;
	int ret;

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(buf, buf_len);
    ZEND_PARSE_PARAMETERS_END();
	
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }
	
	ret = tcc_compile_string(tcc->state, buf);
    if(ret==-1)
    {
        php_tcc_dtor(Z_RES_P(ztcc));
    }
	RETURN_LONG(ret);
}

PHP_FUNCTION(tcc_set_output_type)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	long output_type = TCC_OUTPUT_MEMORY;
	int ret;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_OPTIONAL;
        Z_PARAM_LONG(output_type);
    ZEND_PARSE_PARAMETERS_END();
	
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }
	ret = tcc_set_output_type(tcc->state, output_type);
	RETURN_LONG(ret);
}

PHP_FUNCTION(tcc_output_file)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	char *filename;
	size_t filename_len;
	int ret;
    

    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_STRING(filename,filename_len);
    ZEND_PARSE_PARAMETERS_END();

	
    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(tcc==NULL)
    {
        RETURN_FALSE;
    }
	ret = tcc_output_file(tcc->state, filename);
	RETURN_LONG(ret);
}


PHP_FUNCTION(tcc_run)
{
	zval *ztcc;
	php_tcc *tcc=NULL;
	int ret;
	int i=0;
	zval *zargv=NULL;
	zval **arg;
	HashTable *hash;
    zval *entry;

    ZEND_PARSE_PARAMETERS_START(1, 2)
        Z_PARAM_RESOURCE(ztcc);
        Z_PARAM_OPTIONAL;
        Z_PARAM_ARRAY(zargv);
    ZEND_PARSE_PARAMETERS_END();

    tcc = (php_tcc*)zend_fetch_resource(Z_RES_P(ztcc), PHP_TCC_RES_NAME, le_tcc);
    if(!tcc)
    {
        RETURN_FALSE;
    }
    if(zargv)
    {
		hash = Z_ARRVAL_P(zargv);
        tcc->argc = zend_hash_num_elements(hash);
        tcc->argv = emalloc(sizeof(char*) * tcc->argc);
        ZEND_HASH_FOREACH_VAL(hash, entry)
        {
            tcc->argv[i] = Z_STRVAL_P(entry);
            ++i;
        }
        ZEND_HASH_FOREACH_END();
    }

	ret = tcc_run(tcc->state, tcc->argc, tcc->argv);
	RETURN_LONG(ret);
}


/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(tcc)
{
#if defined(ZTS) && defined(COMPILE_DL_TCC)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(tcc)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "tcc support", "enabled");
	php_info_print_table_end();
}
/* }}} */

PHP_MINIT_FUNCTION(tcc)
{
    le_tcc = zend_register_list_destructors_ex(php_tcc_dtor, NULL, PHP_TCC_RES_NAME, module_number);
    REGISTER_LONG_CONSTANT("TCC_OUTPUT_MEMORY",TCC_OUTPUT_MEMORY, CONST_CS | CONST_PERSISTENT);//1
    REGISTER_LONG_CONSTANT("TCC_OUTPUT_EXE",TCC_OUTPUT_EXE, CONST_CS | CONST_PERSISTENT);//2
    REGISTER_LONG_CONSTANT("TCC_OUTPUT_DLL",TCC_OUTPUT_DLL, CONST_CS | CONST_PERSISTENT);//3
    REGISTER_LONG_CONSTANT("TCC_OUTPUT_OBJ",TCC_OUTPUT_OBJ, CONST_CS | CONST_PERSISTENT);//4
    REGISTER_LONG_CONSTANT("TCC_OUTPUT_PREPROCESS",TCC_OUTPUT_PREPROCESS, CONST_CS | CONST_PERSISTENT);//5
    return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(tcc)
{
    return SUCCESS;
}


/* {{{ tcc_functions[]
 */
static const zend_function_entry tcc_functions[] = {
    PHP_FE(tcc_new,   NULL)
    PHP_FE(tcc_delete,   NULL)
    PHP_FE(tcc_add_sysinclude_path,   NULL)
    PHP_FE(tcc_add_include_path,   NULL)
    PHP_FE(tcc_set_lib_path,   NULL)
    PHP_FE(tcc_add_library_path,   NULL)
    PHP_FE(tcc_add_library,   NULL)
    PHP_FE(tcc_define_symbol,   NULL)
    PHP_FE(tcc_undefine_symbol,   NULL)
    PHP_FE(tcc_add_file,   NULL)
    PHP_FE(tcc_compile_string,   NULL)
    PHP_FE(tcc_set_output_type,   NULL)
    PHP_FE(tcc_output_file,   NULL)
    PHP_FE(tcc_run,   NULL)
    PHP_FE_END
};
/* }}} */

/* {{{ tcc_module_entry
 */
zend_module_entry tcc_module_entry = {
	STANDARD_MODULE_HEADER,
	"tcc",					/* Extension name */
	tcc_functions,			/* zend_function_entry */
    PHP_MINIT(tcc),
    PHP_MSHUTDOWN(tcc),
	PHP_RINIT(tcc),			/* PHP_RINIT - Request initialization */
	NULL,							/* PHP_RSHUTDOWN - Request shutdown */
	PHP_MINFO(tcc),			/* PHP_MINFO - Module info */
	PHP_TCC_VERSION,		/* Version */
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_TCC
# ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
# endif
ZEND_GET_MODULE(tcc)
#endif
